package main

import (
	bolt "Demo/boltDB"
	"log"
)

type BlockChain struct {
	db   *bolt.DB
	tail []byte
}

const blockChainDb = "blockChain.db"
const blockBucket = "blockBucket"

func NewBlockChain(address string) *BlockChain {
	var lasthHash []byte
	db, err := bolt.Open(blockChainDb, 0600, nil)
	if err != nil {
		log.Panic("打开数据库失败")
	}
	db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			bucket, err = tx.CreateBucket([]byte(blockBucket))
			if err != nil {
				log.Panic("创建bucket(" + blockBucket + ")失败")
			}
			genesisBlock := GenesisBlock(address)
			bucket.Put(genesisBlock.Hash, genesisBlock.Serialize())
			bucket.Put([]byte("LastHashKey"), genesisBlock.Hash)
			lasthHash = genesisBlock.Hash
		} else {
			lasthHash = bucket.Get([]byte("LastHashKey"))
		}
		return nil
	})
	return &BlockChain{
		db, lasthHash,
	}
}

func GenesisBlock(address string) *Block {
	coninbase := NewCoinbaseTX(address, "GO 创世区块")
	return NewBlock([]*Transaction{coninbase}, []byte{})
}

func (bc *BlockChain) AddBlock(txs []*Transaction) {
	db := bc.db
	lastHash := bc.tail
	db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockBucket))
		if bucket == nil {
			log.Panic("bucket 不应该为空，请检查")
		}
		block := NewBlock(txs, lastHash)
		bucket.Put(block.Hash, block.Serialize())
		bucket.Put([]byte("LastHashKey"), block.Hash)
		lastHash = block.Hash
		bc.tail = lastHash
		return nil
	})
}

func (bc *BlockChain) FindUTXOs(address string) []TXOutput {
	var UTXO []TXOutput
	spentOutputs := make(map[string][]int64)
	it := bc.NewIterator()
	for {
		block := it.next()
		for _, tx := range block.Transactions {
			//fmt.Printf("current txid :%x\n", tx.TXID)
		OUTPUT:
			for i, output := range tx.TXOutputs {
				//fmt.Printf("current index : %d\n", i)
				if spentOutputs[string(tx.TXID)] != nil {
					for _, j := range spentOutputs[string(tx.TXID)] {
						if int64(i) == j {
							continue OUTPUT
						}
					}
				}
				if output.PubKeyHash == address {
					UTXO = append(UTXO, output)
				}
			}
			if !tx.IScoinbase() {
				for _, input := range tx.TXInputs {
					if input.Sig == address {
						inputArray := spentOutputs[string(input.TXid)]
						var _ = append(inputArray, input.Index)
					}
				}
			}
		}

		if len(block.PrevHash) == 0 {
			break
		}
	}
	return UTXO
}

func (bc *BlockChain) FindNeedUTXOs(from string, amount float64) (map[string][]int64, float64) {
	var calc float64
	var UTXO []TXOutput
	spentOutputs := make(map[string][]int64)
	it := bc.NewIterator()
	for {
		block := it.next()
		for _, tx := range block.Transactions {
			//fmt.Printf("current txid :%x\n", tx.TXID)
		OUTPUT:
			for i, output := range tx.TXOutputs {
				//fmt.Printf("current index : %d\n", i)
				if spentOutputs[string(tx.TXID)] != nil {
					for _, j := range spentOutputs[string(tx.TXID)] {
						if int64(i) == j {
							continue OUTPUT
						}
					}
				}
				if output.PubKeyHash == from {
					UTXO = append(UTXO, output)
					calc = calc + output.Value
				}
			}
			if !tx.IScoinbase() {
				for _, input := range tx.TXInputs {
					if input.Sig == from {
						inputArray := spentOutputs[string(input.TXid)]
						var _ = append(inputArray, input.Index)
					}
				}
			}
		}
		if len(block.PrevHash) == 0 {
			break
		}
	}
	return spentOutputs, calc
}
